{%- set clazz_name = clazz['name'] -%}
{%- set norm_name = clazz['name'] %}
{%- set snake_norm_name = camel_to_snake(norm_name) -%}
{%- set snake_name = camel_to_snake(clazz_name) -%}
{%- set instance_methods = clazz['methods'] | selectattr('is_vararg', 'equalto', false) | selectattr('is_virtual', 'equalto', false) | selectattr('is_static', 'equalto', false) | list -%}
{# {%- set virtual_methods = clazz['methods'] | selectattr('is_vararg', 'equalto', false) | selectattr('is_virtual', 'equalto', true) | selectattr('is_static', 'equalto', false) | list -%} #}
{%- set virtual_methods = [] -%}
{%- set static_methods = clazz['methods'] | selectattr('is_vararg', 'equalto', false) | selectattr('is_virtual', 'equalto', false) | selectattr('is_static', 'equalto', true) | list-%}
{%- set vararg_methods = clazz['methods'] | selectattr('is_vararg', 'equalto', true) | list -%}
{%- if clazz_name in (singletons | map(attribute='type') | list)  %}
	{%- if clazz_name == 'ClassDB' %}
		{%- set norm_name = 'ClassDBSingleton' %}
		{%- set snake_norm_name = camel_to_snake(norm_name) -%}
	{%- endif %}
{%- endif %}
#include "quickjs/quickjs.h"
#include "register/classes/register_classes.h"
#include "quickjs/env.h"
#include "utils/func_utils.hpp"
#include "utils/str_helper.hpp"
#include "utils/quickjs_helper.hpp"
#include "utils/variant_helper.hpp"
{%- if vararg_methods | length > 0 %}
#include "register/classes/{{snake_name}}_vararg_method.hpp"
{%- endif %}
{%- for den in dependency %}
	{%- if den == 'class_db' %}
#include <godot_cpp/classes/class_db_singleton.hpp>
	{%- else %}
#include <godot_cpp/classes/{{den}}.hpp>
	{%- endif %}
{%- endfor %}
#include <godot_cpp/variant/builtin_types.hpp>
{# #include <godot_cpp/core/class_db.hpp> #}

{%- set class_id = norm_name + '::__class_id' %}

using namespace godot;

static void {{snake_name}}_class_finalizer(JSRuntime *rt, JSValue val) {
	{{norm_name}} *{{snake_name}} = reinterpret_cast<{{norm_name}} *>(JS_GetOpaque(val, classes["{{clazz_name}}"]));
	if ({{snake_name}})
		internal::gdextension_interface_object_free_instance_binding({{snake_name}}->_owner, internal::token);
}

static JSClassDef {{snake_name}}_class_def = {
	"{{clazz_name}}",
	.finalizer = {{snake_name}}_class_finalizer
};

static JSValue {{snake_name}}_class_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) {
	JSClassID class_id = classes[{{clazz_name}}::get_class_static()];
	JSValue proto = JS_GetPropertyStr(ctx, new_target, "prototype");
	JSValue obj = JS_NewObjectProtoClass(ctx, proto, class_id);
	if (JS_IsException(obj))
		return obj;
	{%- if clazz_name in (singletons | map(attribute='type') | list) %}
	{{norm_name}} *{{snake_name}}_class = {{norm_name}}::get_singleton();
	{%- else%}

	{{norm_name}} *{{snake_name}}_class;
	if (argc == 1)
		{{snake_name}}_class = static_cast<{{clazz_name}} *>(VariantAdapter(*argv).operator Variant().operator godot::Object *());
	else
		{{snake_name}}_class = memnew({{clazz_name}});

	{%- endif %}
	if (!{{snake_name}}_class) {
		JS_FreeValue(ctx, obj);
		return JS_EXCEPTION;
	}
	JS_SetOpaque(obj, {{snake_name}}_class);
	return obj;
}
{%- macro get_const(method) %}
{{-'_const' if method['is_const'] else ''-}}
{%- endmacro %}

{%- for method in instance_methods + virtual_methods %}
	{%- if clazz_name == 'FileAccess' and method['name'] == 'get_buffer' %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	return call_builtin_const_method_ret(static_cast<PackedByteArray(FileAccess::*)(int64_t) const>(&FileAccess::get_buffer), ctx, this_val, argc, argv);
};
	{% elif clazz_name == 'FileAccess' and method['name'] == 'store_buffer' %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	return call_builtin_method_no_ret(static_cast<void(FileAccess::*)(const PackedByteArray &)>(&FileAccess::store_buffer), ctx, this_val, argc, argv);
};
	{%- else %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
		{%- if method['return_value'] %}
	return call_builtin{{get_const(method)}}_method_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
		{%- else %}
    return call_builtin{{get_const(method)}}_method_no_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
		{%- endif %}
};
	{%- endif %}
{%- endfor %}
{%- for method in static_methods %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
    {%- if method['return_value'] %}
	return call_builtin_static{{get_const(method)}}_method_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
    {%- else %}
    return call_builtin_static{{get_const(method)}}_method_no_ret(&{{norm_name}}::{{method['name']}}, ctx, this_val, argc, argv);
    {%- endif %}
};
{%- endfor %}
{%- for method in vararg_methods %}
static JSValue {{snake_name}}_class_{{method['name']}}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
    {%- if method['return_value'] %}
	return call_builtin_free_owner_vararg_method_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {%- else %}
    return call_builtin_free_owner_vararg_method_no_ret(&js_{{method['name']}}, ctx, this_val, argc, argv);
    {%- endif %}
}
{%- endfor %}

{%- if instance_methods or vararg_methods or virtual_methods %}
static const JSCFunctionListEntry {{snake_name}}_class_proto_funcs[] = {
	{%- for method in instance_methods + vararg_methods + virtual_methods %}
	JS_CFUNC_DEF("{{ method['name'] }}", {{method['arguments'] | length}}, &{{snake_name}}_class_{{method['name']}}),
	{%- endfor %}
};
{%- endif %}

{%- if static_methods %}
static const JSCFunctionListEntry {{snake_name}}_class_static_funcs[] = {
	{%- for method in static_methods %}
	JS_CFUNC_DEF("{{ method['name'] }}", {{method['arguments'] | length}}, &{{snake_name}}_class_{{method['name']}}),
	{%- endfor %}
};
{%- endif %}

{%- for signal in clazz['signals'] %}
static JSValue {{snake_name}}_class_get_{{signal['name']}}_signal(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
	CHECK_INSTANCE_VALID_V(this_val);
	{{clazz['name']}} *opaque = reinterpret_cast<{{clazz['name']}} *>(JS_GetOpaque(this_val, JS_GetClassID(this_val)));
	JSValue js_signal = JS_GetPropertyStr(ctx, this_val, "{{signal['name']}}_signal");
	if (JS_IsUndefined(js_signal)) {
		js_signal = VariantAdapter(Signal(opaque, "{{signal['name']}}"));
		JS_DefinePropertyValueStr(ctx, this_val, "{{signal['name']}}_signal", js_signal, JS_PROP_HAS_VALUE);
	}
	return js_signal;
}
{%- endfor %}

{%- macro getset(gs,method,type) %}
	{%-  if (gs in clazz['methods'] | map(attribute='name')) and type == 'getter' -%}
	JS_NewCFunction(ctx, {{snake_name}}_class_{{gs}}, "{{gs}}", 0)
	{%- elif (gs in clazz['methods'] | map(attribute='name')) and type == 'setter' -%}
	JS_NewCFunction(ctx, {{snake_name}}_class_{{gs}}, "{{gs}}", 1)
	{%- else -%}
	JS_UNDEFINED
	{%- endif -%}
{%- endmacro %}

static void define_{{snake_name}}_property(JSContext *ctx, JSValue proto) {
	{%- for prop in clazz['properties'] %}
		{%- if prop['getter'] in clazz['methods'] | map(attribute='name') or prop['setter'] in clazz['methods'] | map(attribute='name') %}
    JS_DefinePropertyGetSet(
        ctx,
        proto,
        JS_NewAtom(ctx, "{{prop['name']}}"),
        {{ getset(prop['getter'], clazz['methods'], 'getter') }},
        {{ getset(prop['setter'], clazz['methods'], 'setter') }},
        JS_PROP_GETSET
    );
		{%- endif %}
	{%- endfor %}
	{% for signal in clazz['signals'] %}
	JS_DefinePropertyGetSet(
		ctx,
		proto,
		JS_NewAtom(ctx, "{{signal['name']}}"),
		JS_NewCFunction(ctx, {{snake_name}}_class_get_{{signal['name']}}_signal, "get_{{signal['name']}}_signal", 0),
		JS_UNDEFINED,
		JS_PROP_GETSET);
	{% endfor %}
}

static void define_{{snake_name}}_enum(JSContext *ctx, JSValue proto) {
	{%- for enum in clazz['enums'] %}
	JSValue {{enum['name']}}_obj = JS_NewObject(ctx);
		{%- for value in enum['values'] %}
	JS_SetPropertyStr(ctx, {{enum['name']}}_obj, "{{value['name']}}", JS_NewInt64(ctx, {{value['value']}}));
		{%- endfor %}
	JS_SetPropertyStr(ctx, proto, "{{enum['name']}}", {{enum['name']}}_obj);
	{%- endfor %}
}

static int js_{{snake_name}}_class_init(JSContext *ctx, JSModuleDef *m) {
	classes["{{clazz_name}}"] = 0;
	classes["{{clazz_name}}"] = JS_NewClassID(&classes["{{clazz_name}}"]);
	JSClassID class_id = classes["{{clazz_name}}"];
	class_id_list.insert(class_id);
	JS_NewClass(JS_GetRuntime(ctx), class_id, &{{snake_name}}_class_def);

	JSValue proto = JS_NewObjectClass(ctx, class_id);
	{%- if clazz['inherits'] %}
	JSValue base_class = JS_GetClassProto(ctx, classes["{{clazz['inherits']}}"]);
	JS_SetPrototype(ctx, proto, base_class);
	{%- endif %}
	JS_SetClassProto(ctx, class_id, proto);

	define_{{snake_name}}_property(ctx, proto);
	define_{{snake_name}}_enum(ctx, proto);
		{%- if instance_methods or vararg_methods %}
	JS_SetPropertyFunctionList(ctx, proto, {{snake_name}}_class_proto_funcs, _countof({{snake_name}}_class_proto_funcs));
		{%- endif %}
	JSValue ctor = JS_NewCFunction2(ctx, {{snake_name}}_class_constructor, "{{clazz_name}}", 0, JS_CFUNC_constructor, 0);
	{%- if static_methods %}
	JS_SetPropertyFunctionList(ctx, ctor, {{snake_name}}_class_static_funcs, _countof({{snake_name}}_class_static_funcs));
	{%- endif %}
	JS_SetConstructor(ctx, ctor, proto);
	JS_SetModuleExport(ctx, m, "{{clazz_name}}", ctor);

	return 0;
}

{%- set inherits = clazz['inherits'] %}
{%- if clazz['inherits'] == 'Object' %}
{%- set inherits = 'GodotObject' %}
{%- endif %}

JSModuleDef *_js_init_{{ snake_name }}_module(JSContext *ctx, const char *module_name) {
	// 需要提前完成import依赖
	{%- if inherits %}
		{%- if inherits not in singletons | map(attribute='type') | list %}
	const char *code = "import * as _ from '@godot/classes/{{camel_to_snake(inherits)}}';";
	JSValue module = JS_Eval(ctx, code, strlen(code), "<eval>", JS_EVAL_TYPE_MODULE);
	if (JS_IsException(module))
		return NULL;
		{%- endif %}
	{%- endif %}
	JSModuleDef *m = JS_NewCModule(ctx, module_name, js_{{snake_name}}_class_init);
	if (!m)
		return NULL;
	JS_AddModuleExport(ctx, m, "{{clazz_name}}");
	return m;
}

JSModuleDef *js_init_{{ snake_name }}_module(JSContext *ctx) {
	{%- set class_name = clazz['name'] %}
	{%- if clazz['name'] == 'Object' %}
	{%- set class_name = 'GodotObject' %}
	{%- endif %}
	return _js_init_{{ snake_name }}_module(ctx, "@godot/classes/{{camel_to_snake(class_name)}}");
}

void register_{{ snake_name }}() {
	js_init_{{ snake_name }}_module(ctx);
}